/**
 * @file str_utils.c
 * @brief String functions used to deal with strings.
 * @version 1.0
 * @date Out-2011
 * @author Leonardo Lino 2101101
 * @author Joaquim Ferreira 2101104
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "debug.h"
#include "files.h"
#include "hashtables.h"
#include "listas.h"
#include "memory.h"
#include "str_utils.h"

/** @brief ERR_IO 1 - A constant that means that an error occurred.*/
#define ERR_IO 1
/** @brief WORD_OPTION 1 - A constant to use for define a type. */
#define WORD_OPTION 1
/** @brief FREQ_OPTION 2 - A constant to use for define a type. */
#define FREQ_OPTION 2
/** @brief SPELL_OPTION 3 - A constant to use for define a type. */
#define SPELL_OPTION 3

/** @brief Function that check the word lenght and for invalid characters.*/
int validateWord (char *word)
{
  if (strlen (word) >= 2) {
    if ((strchr (word, '_') == NULL) && (strchr (word, '-') == NULL)
	&& (strchr (word, ':') == NULL) && (strchr (word, '"') == NULL)
	&& (strchr (word, ',') == NULL)) {
      return 1;
    } else {
      printf
	("The <word> parameter is invalid, the characters _ - : \" and , are not allowed.\n");
      return 0;
    }
  } else {
    printf ("The <word> parameter is invalid, minimum two characters.\n");
  }
  return 0;
}

/** @brief Function that show 
 * a line with a given word, if the type is WORD_OPTION or 
 * send words to a hashtable, if the type is FREQ_OPTION.
 */
void *getLinesOrWords (char *filename, void *arg, int type)
{
  char *word = NULL;
  HASHTABLE_T *word_hasht = NULL;
  size_t len = 0;
  ssize_t read;
  char *token = NULL, *subtoken = NULL;
  char *saveptr1, *saveptr2;
  char *line = NULL, *subline = NULL;
  LISTA_GENERICA_T* lista=NULL;
  
  if (type == WORD_OPTION)
	word = (char *) arg;

  if (type == FREQ_OPTION)
	word_hasht = (HASHTABLE_T *) arg;
	
  if (type == SPELL_OPTION)
	lista = lista_criar(free);
		

  FILE *file_ptr = MY_FOPEN (filename, "r");

  while ((read = getline (&line, &len, file_ptr)) != -1 && G_continue) {

    for (token = strtok_r (line, ".!?", &saveptr1); token != NULL;token = strtok_r (NULL, ".!?", &saveptr1)) {
      if (token[strlen (token) - 1] == '\n')
        token[strlen (token) - 1] = '\0';
		  
      subline = (char *) MY_MALLOC (sizeof (char) * (strlen (token) + 1));
      strcpy (subline, token);
      subline = strlower (subline);
		  
      for (subtoken = strtok_r (subline, " _-:\",", &saveptr2);subtoken != NULL;subtoken = strtok_r (NULL, " _-:\",", &saveptr2)) {
		  
			if (type == WORD_OPTION && G_continue) {
				if (strlen (subtoken) >= 2 && strcmp (subtoken, word) == 0) {
				printf ("%s\n", token);
				G_proc_lines++;
				break;
				}
			}
				
			if (type == FREQ_OPTION && G_continue) {
				word = trimWord (subtoken);
				if (strlen (word) >= 2)
					add_word_hasht (word_hasht, word);
				else
					MY_FREE(word);
			}
			
			if (type == SPELL_OPTION && G_continue) {
				subtoken=trimWord (subtoken);
				if ((strlen(subtoken))>1){
					lista_inserir(lista, strdup(subtoken));
				}
				G_proc_lines++;
			}
			
      }
      MY_FREE (subline); 
    }
  }
  MY_FREE (line);
  fclose (file_ptr);

  if (type == FREQ_OPTION)
    return word_hasht;
    
  if (type == SPELL_OPTION)
    return lista;

  return NULL;
}

/** @brief Function that convert a string to lower case.*/
char *strlower (char *str)
{
  char *ptr = str;

  while ((*str = tolower (*str))) {
    str++;
  }
  return ptr;
}

/** @brief Function that trim the last character from a word. Used to remove some invalid characters.*/
char *trimWord (char *str)
{
  char *word = NULL;
  int lastch = strlen (str) - 1;
 
  if (str[lastch] == ' ' || str[lastch] == '_' || str[lastch] == '-' || str[lastch] == ';' || str[lastch] == ')'
      || str[lastch] == ':' || str[lastch] == '"' || str[lastch] == ','
      || str[lastch] == '\n') {
    word = (char *) MY_MALLOC (sizeof (char) * strlen (str));
    strncpy (word, str, lastch);
    word[lastch] = '\0';
    } else
        word = strdup (str);
  return word;
}

/** @brief This function outputs a message for invalid number of parameters.*/
void invalid_param (char *arg, char *param)
{
  printf ("Invalid number of parameters. Must be %s %s <filename.txt>\n", arg,
	  param);
}

/** @brief Function that add a key and a value to the hashtable or update an existent value.*/
void add_word_hasht (HASHTABLE_T * hasht, char *word)
{
  FREQ_WORD_T *f_word_elem = NULL, *f_word_aux = NULL;

  /**< @brief If the key (word) do not exits in the hashtable, create a new key and a value (pointer to a type struct FREQ_WORD_T).*/
  if ((f_word_aux = (FREQ_WORD_T *) tabela_consultar (hasht, word)) == NULL) {
    f_word_elem = (FREQ_WORD_T *) MY_MALLOC (sizeof (FREQ_WORD_T));
    f_word_elem->word = word;
    f_word_elem->count = 1;
    tabela_inserir (hasht, f_word_elem->word, f_word_elem);
  } else {
    /**< @brief If the hashkey exits update the value (f_word_aux->count) and free the argument word pointer.*/
    f_word_aux->count++;
    MY_FREE (word);
  }
}

/** @brief Function that convert a hashtable to a linkedlist and show the word histogram (--freq option).*/
void convert_show_histogram (HASHTABLE_T *word_hasht)
{
  FREQ_WORD_T *f_word = NULL;
  LISTA_GENERICA_T *f_word_llist = NULL;
  ITERADOR_T *it = NULL;
  
  /**< @brief Create a linkedlist with the pointers to the structures with the elements.*/
  f_word_llist = tabela_criar_lista_elementos (word_hasht);
  /**< @brief Create an orderly iterator.*/
  it = lista_criar_iterador_ordenado (f_word_llist, (COMPARAR_FUNC) count_word_compare);
    
  printf ("Show the word histogram!\n");
  /**< @brief Show all the words from the orderly list.*/
  while ((f_word = (FREQ_WORD_T *) iterador_proximo_elemento (it)) != NULL && G_continue) {
    printf ("%s:%d\n", f_word->word, f_word->count);
    G_proc_lines++;
  }
    
  /**< @brief Free the memory of the iterator.*/
  iterador_destruir (&it);
  /**< @brief Free the memory of the linkedlist and all the memory from the elements using the function to free the structures.*/
  lista_destruir (&f_word_llist);
  /**< @brief Verify if the pointer of the linkedlist it's NULL.*/
  assert (f_word_llist == NULL);

  /**< @brief Free the memory of the hashtable and all the memory from the keys to the hashtable.*/
  tabela_destruir (&word_hasht);
  /**< @brief Verify if the pointer of the hashtable it's NULL.*/
  assert (word_hasht == NULL);
}


/** @brief Function that compares the number order from two words counters used in linkedlist.*/
int count_word_compare (FREQ_WORD_T * f_word1, FREQ_WORD_T * f_word2)
{
  /**< @brief If the counters are iqual compare alphabetically.*/
  if ((f_word2->count - f_word1->count) == 0)
    return strcmp (f_word1->word, f_word2->word);
  else
    return f_word2->count - f_word1->count; /**< @brief If not compare numerically.*/
}

/** @brief Function that free the allocated memory from the word struct and the struct.*/
void free_freq_word (FREQ_WORD_T *f_word)
{
  /**< @brief Free the allocated memory from the word pointer.*/
  MY_FREE (f_word->word);
  /**< @brief Verify if the pointer of the string it's NULL.*/
  assert (f_word->word == NULL);
  /**< @brief Free the allocated memory from the struct FREQ_WORD_T.*/
  MY_FREE (f_word);
  /**< @brief Verify if the pointer of the structure it's NULL.*/
  assert (f_word == NULL);
}

/** @brief Function that concatenate two strings, one with the path,
 * then the slash '/' and then the other with the directory name or the filename.*/
char *path_cat (char *str1, char *str2)
{
  char *fullPath;
	
  /**< @brief Reserve memory to countain the two strings plus the slash and the terminator.*/
  fullPath = (char *) MY_MALLOC ((strlen (str1) + strlen (str2) + 2) * sizeof (char));
  //printf("fullPath: %p\n", &fullPath);
  /**<  @brief Put into the reserved memory all the strings, the slash and the terminator.*/
  sprintf (fullPath, "%s/%s", str1, str2);
  return fullPath;
}

/** @brief Function that will return the number of digits that a given integer number has.*/
int num_digits (int number)
{
  int digits = 0;
  int step = 1;
  
  while (step <= number) {
    digits++;
    step *= 10;
  }
  return digits ? digits : 1;
}
